import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_sound_record/flutter_sound_record.dart';
import 'package:just_audio/just_audio.dart' as ap;

class AudioPlayer extends StatefulWidget {
  const AudioPlayer({
    required this.source,
    required this.onDelete,
    Key? key,
  }) : super(key: key);

  /// Path from where to play recorded audio
  final ap.AudioSource source;

  /// Callback when audio file should be removed
  /// Setting this to null hides the delete button
  final VoidCallback onDelete;

  @override
  AudioPlayerState createState() => AudioPlayerState();
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<ap.AudioSource>('source', source));
    properties.add(ObjectFlagProperty<VoidCallback>.has('onDelete', onDelete));
  }
}

class AudioPlayerState extends State<AudioPlayer> {
  static const double _controlSize = 56;
  static const double _deleteBtnSize = 24;
  static const int _rewindTolerance = 4;

  final ap.AudioPlayer _audioPlayer = ap.AudioPlayer();
  late StreamSubscription<ap.PlayerState> _playerStateChangedSubscription;
  late StreamSubscription<Duration?> _durationChangedSubscription;
  late StreamSubscription<Duration> _positionChangedSubscription;
  final FlutterSoundRecord _audioRecorder = FlutterSoundRecord();
  int _currentFileSize = 0;
  int _rwOffset = 0; // 每次播放的步进大小
  int _currentPosition = 0; // 当前实际播放位置（累积的步数）
  bool isPlayingFlag = false;
  int _prevRwOffset = 0;
  bool _isUserSeeking = false;
  bool _hasReachedEnd = false;
  int? _pendingSeekOffset;
  bool _wasPlayingBeforeSeek = false;

  @override
  void initState() {
    _playerStateChangedSubscription =
        _audioPlayer.playerStateStream.listen((ap.PlayerState state) async {
      if (state.processingState == ap.ProcessingState.completed) {
        await stop();
      }
      setState(() {});
    });
    _positionChangedSubscription = _audioPlayer.positionStream
        .listen((Duration position) => setState(() {}));
    _durationChangedSubscription = _audioPlayer.durationStream
        .listen((Duration? duration) => setState(() {}));

    isPlayingFlag = false;
    // 设置文件大小变化监听器
    FlutterSoundRecord.setOnFileSizeChanged((int fileSize, int rwOffset) {
      if (!mounted) {
        return;
      }

      final int alignedOffset = _alignOffset(rwOffset);
      bool shouldReset = false;

      setState(() {
        _currentFileSize = fileSize;
        _rwOffset = alignedOffset;

        if (_isUserSeeking) {
          return;
        }

        if (_pendingSeekOffset != null) {
          final int target = _pendingSeekOffset!;
          if (alignedOffset + _rewindTolerance >= target) {
            _pendingSeekOffset = null;
            _currentPosition = alignedOffset;
            _prevRwOffset = alignedOffset;
          } else {
            _currentPosition = target;
            _prevRwOffset = alignedOffset;
          }
          return;
        }

        if (_currentFileSize > 0) {
          final int loopFloor = (_currentFileSize * 4) ~/ 5;
          if (_prevRwOffset > loopFloor &&
              alignedOffset + _rewindTolerance < _prevRwOffset &&
              !_hasReachedEnd) {
            shouldReset = true;
            return;
          }
        }

        _currentPosition = alignedOffset;
        _prevRwOffset = alignedOffset;

        if (_currentFileSize > 0 &&
            alignedOffset < _currentFileSize - _rewindTolerance) {
          _hasReachedEnd = false;
        }

        if (_currentFileSize > 0 && _isNearEnd(alignedOffset) && !_hasReachedEnd) {
          shouldReset = true;
        }
      });

      if (shouldReset) {
        _resetOhPlayback();
      }
    });

    _init();
    super.initState();
  }

  Future<void> _init() async {
    if (defaultTargetPlatform != TargetPlatform.ohos) {
      await _audioPlayer.setAudioSource(widget.source);
    } else {
      await _audioRecorder.initRenderer();
    }
  }

  @override
  void dispose() {
    _playerStateChangedSubscription.cancel();
    _positionChangedSubscription.cancel();
    _durationChangedSubscription.cancel();
    _audioPlayer.dispose();
    _audioRecorder.releaseRender();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        return Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: <Widget>[
            _buildControl(),
            _buildSlider(constraints.maxWidth),
            IconButton(
              icon: const Icon(
                Icons.delete,
                color: Color(0xFF73748D),
                size: _deleteBtnSize,
              ),
              onPressed: () async {
                // ignore: always_specify_types
                if (defaultTargetPlatform != TargetPlatform.ohos) {
                  _audioPlayer.stop().then((value) => widget.onDelete());
                } else {
                  await _audioRecorder.stopRender();
                  widget.onDelete();
                }
              },
            ),
          ],
        );
      },
    );
  }

  Widget _buildControl() {
    Icon icon;
    Color color;
    if (defaultTargetPlatform != TargetPlatform.ohos) {
      if (_audioPlayer.playerState.playing) {
        icon = const Icon(Icons.pause, color: Colors.red, size: 30);
        color = Colors.red.withOpacity(0.1);
      } else {
        final ThemeData theme = Theme.of(context);
        icon = Icon(Icons.play_arrow, color: theme.primaryColor, size: 30);
        color = theme.primaryColor.withOpacity(0.1);
      }
      return ClipOval(
        child: Material(
          color: color,
          child: InkWell(
            child: SizedBox(
                width: _controlSize, height: _controlSize, child: icon),
            onTap: () {
              if (_audioPlayer.playerState.playing) {
                pause();
              } else {
                play();
              }
            },
          ),
        ),
      );
    } else {
      if (isPlayingFlag) {
        icon = const Icon(Icons.pause, color: Colors.red, size: 30);
        color = Colors.red.withOpacity(0.1);
      } else {
        final ThemeData theme = Theme.of(context);
        icon = Icon(Icons.play_arrow, color: theme.primaryColor, size: 30);
        color = theme.primaryColor.withOpacity(0.1);
      }
      return ClipOval(
        child: Material(
          color: color,
          child: InkWell(
            child: SizedBox(
                width: _controlSize, height: _controlSize, child: icon),
            onTap: () {
              if (isPlayingFlag) {
                _audioRecorder.pauseRenderer();
                setState(() {
                  isPlayingFlag = false;
                });
                return;
              }

              final bool wasEnded = _hasReachedEnd;
              if (wasEnded) {
                _audioRecorder.seek(0);
              }
              _audioRecorder.playing();
              setState(() {
                isPlayingFlag = true;
                _hasReachedEnd = false;
                _isUserSeeking = false;
                _pendingSeekOffset = null;
                if (wasEnded) {
                  _currentPosition = 0;
                  _rwOffset = 0;
                  _prevRwOffset = 0;
                }
              });
            },
          ),
        ),
      );
    }
  }

  Widget _buildSlider(double widgetWidth) {
    if (defaultTargetPlatform != TargetPlatform.ohos) {
      final Duration position = _audioPlayer.position;
      final Duration? duration = _audioPlayer.duration;
      bool canSetValue = false;
      if (duration != null) {
        canSetValue = position.inMilliseconds > 0;
        canSetValue &= position.inMilliseconds < duration.inMilliseconds;
      }

      double width = widgetWidth - _controlSize - _deleteBtnSize;
      width -= _deleteBtnSize;

      return SizedBox(
        width: width,
        child: Slider(
          activeColor: Theme.of(context).primaryColor,
          inactiveColor: Theme.of(context).colorScheme.secondary,
          onChanged: (double v) {
            if (duration != null) {
              final double position = v * duration.inMilliseconds;
              _audioPlayer.seek(Duration(milliseconds: position.round()));
            }
          },
          value: canSetValue && duration != null
              ? position.inMilliseconds / duration.inMilliseconds
              : 0.0,
        ),
      );
    } else {
      double width = widgetWidth - _controlSize - _deleteBtnSize;
      width -= _deleteBtnSize;
      return _currentFileSize <= 0
          ? SizedBox(
              width: width,
              child: Slider(value: 0, onChanged: (double value) {}),
            )
          : SizedBox(
              width: width,
              child: Slider(
                activeColor: Theme.of(context).primaryColor,
                inactiveColor: Theme.of(context).colorScheme.secondary,
                max: _currentFileSize.toDouble(),
                value: _currentPosition
                    .toDouble()
                    .clamp(0.0, _currentFileSize.toDouble()),
                onChangeStart: (_) {
                  _wasPlayingBeforeSeek = isPlayingFlag;
                  if (_wasPlayingBeforeSeek) {
                    _audioRecorder.pauseRenderer();
                  }
                  setState(() {
                    _isUserSeeking = true;
                  });
                },
                onChanged: (double value) {
                  final int offset = _alignOffset(value.toInt());
                  _audioRecorder.seek(offset);

                  setState(() {
                    _currentPosition = offset;
                    _rwOffset = offset;
                    _pendingSeekOffset = offset;
                    _prevRwOffset = offset;
                    _hasReachedEnd = false;
                  });
                },
                onChangeEnd: (double value) {
                  final int offset = _alignOffset(value.toInt());
                  _audioRecorder.seek(offset);
                  final bool reachedEnd =
                      _currentFileSize > 0 && _isNearEnd(offset);
                  final bool shouldResume = _wasPlayingBeforeSeek && !reachedEnd;
                  setState(() {
                    _isUserSeeking = false;
                    _pendingSeekOffset = offset;
                    if (shouldResume) {
                      isPlayingFlag = true;
                      _hasReachedEnd = false;
                    }
                  });

                  if (reachedEnd) {
                    _resetOhPlayback();
                  } else if (shouldResume) {
                    _audioRecorder.playing();
                  }

                  _wasPlayingBeforeSeek = false;
                },
              ),
            );
    }
  }

  int _alignOffset(int offset) {
    return offset.isOdd ? offset - 1 : offset;
  }

  bool _isNearEnd(int offset) {
    return offset >= (_currentFileSize - _rewindTolerance).clamp(0, _currentFileSize);
  }

  void _resetOhPlayback() {
    _audioRecorder.pauseRenderer();
    _audioRecorder.seek(0);
    if (!mounted) {
      return;
    }
    setState(() {
      isPlayingFlag = false;
      _currentPosition = 0;
      _rwOffset = 0;
      _prevRwOffset = 0;
      _hasReachedEnd = true;
      _isUserSeeking = false;
      _pendingSeekOffset = null;
    });
  }

  Future<void> play() {
    return _audioPlayer.play();
  }

  Future<void> pause() {
    return _audioPlayer.pause();
  }

  Future<void> stop() async {
    await _audioPlayer.stop();
    return _audioPlayer.seek(Duration.zero);
  }
}
